from __future__ import annotations

import time
from typing import Iterable, Callable

from .config import RuntimeConfig
from .provider import EmailMessage, EmailProvider
from .queue import MailQueue
from .exceptions import MailerException, SMTPConnectionError, SMTPAuthenticationError
from .logger import logger


class RateLimiter:
    def __init__(self, per_sec: int) -> None:
        self._interval = 1.0 / max(1, per_sec)
        self._last = 0.0

    def acquire(self) -> None:
        now = time.time()
        elapsed = now - self._last
        if elapsed < self._interval:
            time.sleep(self._interval - elapsed)
        self._last = time.time()


class MailDispatcher:
    def __init__(self, queue: MailQueue, provider: EmailProvider, cfg: RuntimeConfig) -> None:
        self._queue = queue
        self._provider = provider
        self._cfg = cfg
        self._rl = RateLimiter(cfg.rate_limit_per_sec)

    def _send_once(self, msg: EmailMessage) -> str:
        return self._provider.send(msg)

    def _should_retry(self, tries: int, max_retries: int) -> bool:
        return tries < max_retries

    def run_once(self, dry_run: bool = False) -> int:
        items = self._queue.fetch_batch(self._cfg.batch_size)
        logger.info(f"Fetched {len(items)} items from queue for processing")
        sent = 0
        for it in items:
            payload = it.payload
            # 载荷应包含: subject, body_text/body_html, to/cc/bcc
            msg = EmailMessage(
                subject=payload.get("subject", "(no subject)"),
                body_text=payload.get("body_text"),
                body_html=payload.get("body_html"),
                to=payload.get("to", []),
                cc=payload.get("cc"),
                bcc=payload.get("bcc"),
                attachments=payload.get("attachments"),
            )
            try:
                if dry_run:
                    provider_id = "dry-run"
                    logger.info(f"[DRY-RUN] Would send email {it.id}: {msg.subject}")
                else:
                    self._rl.acquire()
                    provider_id = self._send_once(msg)
                    logger.info(f"Successfully sent email {it.id}, provider_id={provider_id}")
                self._queue.mark_done(it.id, provider_id)
                sent += 1
            except SMTPAuthenticationError as e:
                # Authentication errors should not retry
                logger.error(f"Authentication failed for email {it.id}: {str(e)}")
                self._queue.mark_failed(it.id, f"Authentication failed: {str(e)}", can_retry=False)
            except SMTPConnectionError as e:
                # Connection errors can retry
                can_retry = self._should_retry(it.tries + 1, it.max_retries)
                logger.warning(f"Connection error for email {it.id}, retry={can_retry}: {str(e)}")
                self._queue.mark_failed(it.id, f"Connection error: {str(e)}", can_retry)
            except MailerException as e:
                # Other mailer exceptions can retry
                can_retry = self._should_retry(it.tries + 1, it.max_retries)
                logger.warning(f"Send error for email {it.id}, retry={can_retry}: {str(e)}")
                self._queue.mark_failed(it.id, f"Send error: {str(e)}", can_retry)
            except Exception as e:
                # Unexpected errors should not retry
                logger.error(f"Unexpected error for email {it.id}: {str(e)}", exc_info=True)
                self._queue.mark_failed(it.id, f"Unexpected error: {str(e)}", can_retry=False)
        logger.info(f"Completed processing batch: sent={sent}, total={len(items)}")
        return sent


